package com.duomi.lang;

import java.util.*;

import static com.duomi.lang.Utils.*;

class Functions {

    private Functions() {
    }

    static class CompareFunc implements DuomiFunction {

        private final Predicate<Integer> predicate;

        CompareFunc(Predicate<Integer> predicate) {
            this.predicate = predicate;
        }

        @Override
        public Object invoke(List<Object> arguments) {
            except("compare function at least 2 args", arguments.size() >= 2);
            Comparable left = exceptAs(arguments.get(0), Comparable.class);

            final Class<?> typeCheck = left.getClass();
            checkType(arguments, typeCheck);

            for (int i = 1; i < arguments.size(); i++) {
                final Comparable right = exceptAs(arguments.get(i), Comparable.class);
                //noinspection unchecked
                if (!predicate.test(left.compareTo(right))) {
                    return false;
                }
                left = right;
            }

            return true;
        }
    }

    private static void checkType(List<Object> args, Class<?> type) {
        for (Object arg : args) {
            if (!type.isAssignableFrom(arg.getClass())) {
                throw new IllegalStateException("checkType failed");
            }
        }
    }

    enum MathOperator {
        ADD {
            @Override
            int calculate(int left, int right) {
                return left + right;
            }

            @Override
            float calculate(float left, float right) {
                return left + right;
            }

            @Override
            double calculate(double left, double right) {
                return left + right;
            }

            @Override
            long calculate(long left, long right) {
                return left + right;
            }
        },
        SUB {
            @Override
            int calculate(int left, int right) {
                return left - right;
            }

            @Override
            float calculate(float left, float right) {
                return left - right;
            }

            @Override
            double calculate(double left, double right) {
                return left - right;
            }

            @Override
            long calculate(long left, long right) {
                return left - right;
            }
        },
        MULTI {
            @Override
            int calculate(int left, int right) {
                return left * right;
            }

            @Override
            float calculate(float left, float right) {
                return left * right;
            }

            @Override
            double calculate(double left, double right) {
                return left * right;
            }

            @Override
            long calculate(long left, long right) {
                return left * right;
            }
        },
        DIVIDE {
            @Override
            int calculate(int left, int right) {
                return left / right;
            }

            @Override
            float calculate(float left, float right) {
                return left / right;
            }

            @Override
            double calculate(double left, double right) {
                return left / right;
            }

            @Override
            long calculate(long left, long right) {
                return left / right;
            }
        },
        MOD {
            @Override
            int calculate(int left, int right) {
                return left % right;
            }

            @Override
            float calculate(float left, float right) {
                return left % right;
            }

            @Override
            double calculate(double left, double right) {
                return left % right;
            }

            @Override
            long calculate(long left, long right) {
                return left % right;
            }
        };

        abstract int calculate(int left, int right);

        abstract long calculate(long left, long right);

        abstract float calculate(float left, float right);

        abstract double calculate(double left, double right);
    }

    static class MathFunc implements DuomiFunction {

        private final MathOperator operator;

        private static class Collector {
            Number left;

            Collector(Number left) {
                this.left = left;
            }

            void calculate(MathOperator op, Number right) {
                if (left instanceof Double || right instanceof Double) {
                    left = op.calculate(left.doubleValue(), right.doubleValue());
                    return;
                }

                if (left instanceof Float || right instanceof Float) {
                    left = op.calculate(left.floatValue(), right.floatValue());
                    return;
                }

                if (left instanceof Long || right instanceof Long) {
                    left = op.calculate(left.longValue(), right.longValue());
                    return;
                }

                if (left instanceof Integer && right instanceof Integer) {
                    left = op.calculate(left.intValue(), right.intValue());
                    return;
                }

                throw new IllegalStateException("can not calculate: left = " + left.getClass() + ", right = " + right.getClass());
            }
        }

        public MathFunc(MathOperator operator) {
            this.operator = operator;
        }

        @Override
        public Object invoke(List<Object> arguments) {
            checkType(arguments, Number.class);
            except("MathFunc at least has 2 args", arguments.size() >= 2);

            final Collector collector = new Collector(((Number) arguments.get(0)));
            for (int i = 1; i < arguments.size(); i++) {
                collector.calculate(operator, ((Number) arguments.get(i)));
            }
            return collector.left;
        }
    }

    enum BoolOperator {
        And,
        Or,
        Not
    }

    static class BoolLogicFunc implements DuomiFunction {

        private final BoolOperator operator;

        BoolLogicFunc(BoolOperator operator) {
            this.operator = operator;
        }

        @Override
        public Object invoke(List<Object> arguments) {
            switch (operator) {
                case And:
                    return handleAnd(arguments);
                case Or:
                    return handleOr(arguments);
                case Not:
                    except("not should only has 1 arg", arguments.size() == 1);
                    return !bool(arguments.get(0));
            }

            throw new AssertionError("impossible");
        }

        private boolean handleOr(List<Object> arguments) {
            for (Object argument : arguments) {
                if (bool(argument)) {
                    return true;
                }

            }
            return false;
        }

        private boolean handleAnd(List<Object> arguments) {
            for (Object argument : arguments) {
                if (!bool(argument)) {
                    return false;
                }
            }
            return true;
        }
    }

    static class PrintFunction implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            System.out.print(arguments);
            return null;
        }
    }

    static class PrintlnFunction implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            System.out.println(arguments);
            return null;
        }
    }

    static class InstanceOfFunc implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            except("instanceOf Func need 2 args", arguments.size() == 2);
            final Object obj = arguments.get(0);
            final Class clazz = exceptAs(arguments.get(1), Class.class);
            return clazz.isInstance(obj);
        }
    }

    static class DictFunc implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            except("dict func arguments must be even", arguments.size() % 2 == 0);
            final HashMap<Object, Object> map = new HashMap<>();
            for (int i = 0; i < arguments.size(); i += 2) {
                final Object key = arguments.get(i);
                final Object value = arguments.get(i + 1);
                map.put(key, value);
            }
            return map;
        }
    }

    static class ListFunc implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            return new ArrayList<>(arguments);
        }
    }

    static class HashSetFunc implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            return new HashSet<>(arguments);
        }
    }

    static class AssertFunction implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            except("assert has 1 or 2 args", arguments.size() > 0 && arguments.size() <= 2);
            if (!bool(arguments.get(0))) {
                throw new DuomiEvalException("assert failed, on test: " + arguments.get(0));
            }
            return null;
        }
    }

    static class BoolFunction implements DuomiFunction {
        @Override
        public Object invoke(List<Object> arguments) {
            except("bool? func only has 1 arguments", arguments.size() == 1);
            return bool(arguments.get(0));
        }
    }
}
